/*******************************************************************************
 * Copyright (c) 2006 Sybase, Inc. and others.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Sybase, Inc. - initial API and implementation
 *******************************************************************************/
package com.ust.jst.pagedesigner.editors;

import java.io.File;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtension;
import org.eclipse.core.runtime.IExtensionPoint;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Platform;
import org.eclipse.gef.DefaultEditDomain;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.ui.views.palette.PalettePage;
import org.eclipse.gef.ui.views.palette.PaletteViewerPage;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.TextSelection;
import org.eclipse.jface.viewers.IPostSelectionProvider;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jst.jsf.common.ui.internal.logging.Logger;
import org.eclipse.jst.jsf.common.ui.internal.utils.ResourceUtils;
import org.eclipse.jst.pagedesigner.jsp.core.pagevar.IPageVariablesProvider;
import org.eclipse.jst.pagedesigner.jsp.core.pagevar.adapter.IDocumentPageVariableAdapter;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.IPropertyListener;
import org.eclipse.ui.IStorageEditorInput;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.contexts.IContextService;
import org.eclipse.ui.part.MultiPageEditorPart;
import org.eclipse.ui.part.MultiPageEditorSite;
import org.eclipse.ui.part.MultiPageSelectionProvider;
import org.eclipse.ui.texteditor.IDocumentProvider;
import org.eclipse.ui.views.contentoutline.IContentOutlinePage;
import org.eclipse.ui.views.properties.IPropertySheetPage;
import org.eclipse.ui.views.properties.tabbed.ITabbedPropertySheetPageContributor;
import org.eclipse.wst.sse.core.StructuredModelManager;
import org.eclipse.wst.sse.core.internal.provisional.IModelManager;
import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
import org.eclipse.wst.sse.ui.StructuredTextEditor;
import org.eclipse.wst.sse.ui.internal.provisional.extensions.ISourceEditingTextTools;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel;
import org.eclipse.wst.xml.ui.internal.provisional.IDOMSourceEditingTextTools;
import org.w3c.dom.Document;

import com.ust.jst.pagedesigner.IJMTConstants;
import com.ust.jst.pagedesigner.PDPlugin;
import com.ust.jst.pagedesigner.dnd.internal.DesignerSourceMouseTrackAdapter;
import com.ust.jst.pagedesigner.editors.IWPEPersistenceListener.IPersistenceEvent;
import com.ust.jst.pagedesigner.editors.IWPEPersistenceListener.PersistenceEventType;
import com.ust.jst.pagedesigner.editors.actions.DesignPageActionContributor;
import com.ust.jst.pagedesigner.editors.pagedesigner.PageDesignerResources;
import com.ust.jst.pagedesigner.parts.DocumentEditPart;
import com.ust.jst.pagedesigner.preview.PreviewHandlerNew;
import com.ust.jst.pagedesigner.preview.WindowsIEBrowser;
import com.ust.jst.pagedesigner.properties.WPETabbedPropertySheetPage;
import com.ust.jst.pagedesigner.tools.RangeSelectionTool;
import com.ust.jst.pagedesigner.ui.common.PartActivationHandler;
import com.ust.jst.pagedesigner.ui.common.sash.SashEditorPart;
import com.ust.jst.pagedesigner.ui.preferences.PDPreferences;
import com.ust.jst.pagedesigner.utils.EditorUtil;
import com.ust.jst.pagedesigner.utils.PreviewUtil;

/**
 * The HTMLEditor is a multi paged editor. It will use the StructuredTextEditor
 * as the chief editor, and delegate most operations to it.
 *
 * @author mengbo
 */
public final class HTMLEditor extends MultiPageEditorPart implements
        IPropertyListener, ITabbedPropertySheetPageContributor {
    // private static final String PAGE_NAME_DESIGN = "Design"; //$NON-NLS-1$
    // private static final String PAGE_NAME_SOURCE = "Source"; //$NON-NLS-1$
    /**
     * Tabbed property contributor id for WPE
     */
    public final static String TABBED_PROPERTIES_CONTRIBUTOR_ID = "com.ust.jst.pagedesigner.tabPropertyContributor"; //$NON-NLS-1$

    // four different modes for the designer when displayed in a sash editor.
    /**
     * editor split is vertical
     */
    public static final int MODE_SASH_VERTICAL = 0;

    /**
     * editor split is horizontal
     */
    public static final int MODE_SASH_HORIZONTAL = 1;

    /**
     * no split, only designer canvas
     */
    public static final int MODE_DESIGNER = 2;

    /**
     * no split, only SSE source
     */
    public static final int MODE_SOURCE = 3;

    private Logger _log = PDPlugin.getLogger(HTMLEditor.class);

    private boolean _sash = true;

    private int _mode = 0;

    private SashEditorPart _sashEditorPart = null;

    private int _previewPageIndex;

    /** The design viewer */
    private SimpleGraphicalEditor _designViewer;

    /** The text editor. */
    private StructuredTextEditor _textEditor;

    private PartActivationHandler _partListener;

    private PaletteViewerPage _paletteViewerPage;

    private DefaultEditDomain _editDomain;

    private WindowsIEBrowser _browser;

    private Composite _previewComposite;

    private List PREVIEW_FILES_LIST = new ArrayList();

    private IPropertySheetPage _tabbedPropSheet;

    private ISelectionChangedListener _selChangedListener;

    private DesignPageActionContributor _designPageActionContributor;

    private IStructuredModel _model;

    // TODO:This class is never used locally
//	private class TextInputListener implements ITextInputListener {
//		public void inputDocumentAboutToBeChanged(IDocument oldInput,
//				IDocument newInput) {
//            // do nothing
//		}
//
//		public void inputDocumentChanged(IDocument oldInput, IDocument newInput) {
//			if (_designViewer != null && newInput != null)
//				_designViewer.setModel(getModel());
//		}
//	}

    private List<IWPEPersistenceListener> persistenceListeners;

    /**
     * Default constructor
     */
    public HTMLEditor() {
        super();
    }

    /*
     * This method is just to make firePropertyChanged accessbible from some
     * (anonomous) inner classes.
     */
    private void _firePropertyChange(int property) {
        super.firePropertyChange(property);
    }

    /*
     * (non-Javadoc)
     *
     * @see com.ibm.xtools.common.ui.properties.ITabbedPropertySheetPageContributor#getContributorId()
     */
    @Override
    public String getContributorId() {
        return TABBED_PROPERTIES_CONTRIBUTOR_ID;
    }

    private void connectSashPage() {
        ISelectionProvider selectionProvider = this._sashEditorPart.getSite()
                .getSelectionProvider();
        if (selectionProvider instanceof IPostSelectionProvider) {
            ((IPostSelectionProvider) selectionProvider)
                    .addPostSelectionChangedListener(this.getSelectionChangedListener(selectionProvider));
        } else {
            selectionProvider
                    .addSelectionChangedListener(this.getSelectionChangedListener(selectionProvider));
        }
    }

    private void disconnectSashPage() {
        //attempted fix for bug 283569... was not able to repro, but should protect against NPE
        if (this._sashEditorPart != null
                && this._sashEditorPart.getSite() != null
                && this._sashEditorPart.getSite().getSelectionProvider() != null
                && this._selChangedListener != null) {

            final ISelectionProvider selectionProvider = this._sashEditorPart.getSite()
                    .getSelectionProvider();
            if (selectionProvider != null) {
                if (selectionProvider instanceof IPostSelectionProvider) {
                    ((IPostSelectionProvider) selectionProvider)
                            .removePostSelectionChangedListener(this.getSelectionChangedListener(selectionProvider));
                } else {
                    selectionProvider
                            .removeSelectionChangedListener(this.getSelectionChangedListener(selectionProvider));
                }
            }
        }
    }

    private ISelectionChangedListener getSelectionChangedListener(ISelectionProvider selectionProvider) {
        if (this._selChangedListener  == null) {
            if (selectionProvider instanceof IPostSelectionProvider) {
                this._selChangedListener =  new ISelectionChangedListener() {
                @Override
                public void selectionChanged(SelectionChangedEvent event) {
                        ((MultiPageSelectionProvider) HTMLEditor.this.getSite()
                                .getSelectionProvider())
                                .firePostSelectionChanged(event);
                    }
                };
            }
            else {
                this._selChangedListener =  new ISelectionChangedListener() {
                    @Override
                    public void selectionChanged(SelectionChangedEvent event) {
                            ((MultiPageSelectionProvider) HTMLEditor.this.getSite()
                                    .getSelectionProvider())
                                    .firePostSelectionChanged(event);
                        }
                    };
            }
        }
        return this._selChangedListener;
    }

    /**
     * Creates the source page of the multi-page editor.
     * @throws PartInitException
     */
    protected void sash_createAndAddDesignSourcePage() throws PartInitException {
        // create source page
        this._textEditor = this.createTextEditor();
        this._textEditor.setEditorPart(this);
        this._textEditor.addPropertyListener(this);
        // create design page
        this._designViewer = new SimpleGraphicalEditor(this, this.getEditDomain());

        // create SashEditor
        this._sashEditorPart = new SashEditorPart() {
            @Override
            protected void createPages() throws PartInitException {
                this.addPage(HTMLEditor.this._designViewer, this.getEditorInput());
                this.addPage(HTMLEditor.this._textEditor, this.getEditorInput());
            }
        };
        int sashIndex = this.addPage(this._sashEditorPart, this.getEditorInput());

        // Set the sash editor mode from the stored file property
        // or the default preference
        this.initDesignerMode();

        this.setPageText(sashIndex, PDPlugin.getResourceString("HTMLEditor.Design")); //$NON-NLS-1$

        // the update's critical, to get viewer selection manager and
        // highlighting to work
        this._textEditor.update();

        this.firePropertyChange(PROP_TITLE);

        // Changes to the Text Viewer's document instance should also force an
        // input refresh
        // _textEditor.getTextViewer().addTextInputListener(new
        // TextInputListener());
        this.connectSashPage();
    }

    /**
     * @see org.eclipse.ui.part.MultiPageEditorPart#createSite(org.eclipse.ui.IEditorPart)
     */
    @Override
    protected IEditorSite createSite(IEditorPart editor) {
        return new MultiPageEditorSite(this, editor);
    }

    private void tabbed_createAndAddDesignSourcePage()
            throws PartInitException {
        // create source page
        this._textEditor = this.createTextEditor();
        this._textEditor.setEditorPart(this);
        this._textEditor.addPropertyListener(this);

        // create design page
        SimpleGraphicalEditor editor = new SimpleGraphicalEditor(this,
                this.getEditDomain());

        // add design page
        int designPageIndex = this.addPage(editor, null);

        this._designViewer = editor;
        // // note: By adding the design page as a Control instead of an
        // // IEditorPart, page switches will indicate
        // // a "null" active editor when the design page is made active
        this.setPageText(designPageIndex, PDPlugin
                .getResourceString("HTMLEditor.Design")); //$NON-NLS-1$

        // add source page
        int sourcePageIndex = this.addPage(this._textEditor, this.getEditorInput());
        this.setPageText(sourcePageIndex, PDPlugin
                .getResourceString("HTMLEditor.Source")); //$NON-NLS-1$
        // the update's critical, to get viewer selection manager and
        // highlighting to work
        this._textEditor.update();

        this.firePropertyChange(PROP_TITLE);

        // Changes to the Text Viewer's document instance should also force an
        // input refresh
        // _textEditor.getTextViewer().addTextInputListener(new
        // TextInputListener());
    }

    private void createAndAddPreviewPage() {
        this._previewComposite = new Composite(this.getContainer(), 0);
        FillLayout filllayout = new FillLayout();
        this._previewComposite.setLayout(filllayout);

        this._previewPageIndex = this.addPage(this._previewComposite);
        // JSPSourceEditor.Page.Preview.PageText=Preview
        this.setPageText(this._previewPageIndex, PageDesignerResources.getInstance()
                .getString("JSPSourceEditor.Page.Preview.PageText")); //$NON-NLS-1$

    }

    private WindowsIEBrowser getPreviewBrowser() {
        if (this._browser == null) {
            this._browser = new WindowsIEBrowser();
            if (this._browser != null) {
                this._browser.create(this._previewComposite, SWT.NONE);
                this._previewComposite.layout();
            }
        }
        return this._browser;
    }
    /**
     * Connects the design viewer with the viewer selection manager. Should be
     * done after createSourcePage() is done because we need to get the
     * ViewerSelectionManager from the TextEditor. setModel is also done here
     * because getModel() needs to reference the TextEditor.
     */
    protected void connectDesignPage() {
        if (this._designViewer != null) {
            this._designViewer.setModel(this.getModel());
            // _designViewer.getSynchronizer().listenToModel(getModel());
            ISelectionProvider designSelectionProvider = this._designViewer
                    .getSite().getSelectionProvider();
            if (designSelectionProvider instanceof IPostSelectionProvider) {
                ((IPostSelectionProvider) designSelectionProvider)
                        .addPostSelectionChangedListener(new ISelectionChangedListener() {
                            @Override
                            public void selectionChanged(
                                    SelectionChangedEvent event) {
                                if (HTMLEditor.this.getActiveEditor() != HTMLEditor.this._textEditor) {
                                    HTMLEditor.this._designViewer.getSynchronizer()
                                            .selectionChanged(event);
                                }
                            }
                        });
            } else {
                designSelectionProvider
                        .addSelectionChangedListener(new ISelectionChangedListener() {
                            @Override
                            public void selectionChanged(
                                    SelectionChangedEvent event) {
                                if (HTMLEditor.this.getActiveEditor() != HTMLEditor.this._textEditor) {
                                    HTMLEditor.this._designViewer.getSynchronizer()
                                            .selectionChanged(event);
                                }
                            }
                        });
            }
            ISelectionProvider textSelectionProvider = this._textEditor.getSite()
                    .getSelectionProvider();
            if (textSelectionProvider instanceof IPostSelectionProvider) {
                ((IPostSelectionProvider) textSelectionProvider)
                        .addPostSelectionChangedListener(new ISelectionChangedListener() {
                            @Override
                            public void selectionChanged(
                                    SelectionChangedEvent event) {
                                if (event.getSelection() instanceof TextSelection) {
                                    TextSelection textSelection = ((TextSelection) event
                                            .getSelection());
                                    HTMLEditor.this._designViewer
                                            .getSynchronizer()
                                            .textSelectionChanged(
                                                    textSelection.getOffset(),
                                                    textSelection.getOffset()
                                                            + textSelection
                                                                    .getLength());
                                }
                            }
                        });
            } else {
                textSelectionProvider
                        .addSelectionChangedListener(new ISelectionChangedListener() {
                            @Override
                            public void selectionChanged(
                                    SelectionChangedEvent event) {
                                TextSelection textSelection = ((TextSelection) event
                                        .getSelection());
                                HTMLEditor.this._designViewer.getSynchronizer()
                                        .textSelectionChanged(
                                                textSelection.getOffset(),
                                                textSelection.getOffset()
                                                        + textSelection
                                                                .getLength());
                            }
                        });
            }
        }
    }

    /**
     * Creates the pages of this multi-page editor.
     * <p>
     * Subclasses of <code>MultiPageEditor</code> must implement this method.
     * </p>
     */
    @Override
    protected void createPages() {
        try {
            // source page MUST be created before design page, now
            if (this._sash) {
                this.sash_createAndAddDesignSourcePage();
            } else {
                this.tabbed_createAndAddDesignSourcePage();
            }
            this.connectDesignPage();
            this.createAndAddPreviewPage();
            DesignerSourceMouseTrackAdapter adapter = new DesignerSourceMouseTrackAdapter(
                    this._textEditor, this.getEditDomain());
            this._textEditor.getTextViewer().getTextWidget().addMouseListener(
                    adapter);
            this._textEditor.getTextViewer().getTextWidget().addMouseMoveListener(
                    adapter);
        } catch (PartInitException exception) {
            //$NON-NLS-1$ = "An error has occurred when initializing the input for the the editor's source page."
            if (this._log != null) {
                // throw new SourceEditingRuntimeException(
                // "An error has occurred when initializing the input for the
                // the editor's source page.");
            }
        }
        // TODO: add a catch block here for any exception the design
        // page throws and convert it into a more informative message.
    }

    /**
     * Method createTextEditor.
     *
     * @return StructuredTextEditor
     */
    protected StructuredTextEditor createTextEditor() {
        return new DesignerStructuredTextEditorJSP() {
            @Override
            protected void performRevert() {
                super.performRevert();
                HTMLEditor.this.firePersistenceEvent(PersistenceEventType.REVERTED);
            }
        };
    }

    private void disconnectDesignPage() {
        if (this._designViewer != null) {
            this._designViewer.setModel(null);
            this._designViewer.dispose();
        }
    }

    @Override
    public void dispose() {
        //System.out.println("dispose of HTML Editor");
        this.deletePreviewFiles();

        this.disconnectSashPage();
        this.disconnectDesignPage();

        IWorkbenchWindow window = this.getSite().getWorkbenchWindow();
        window.getPartService().removePartListener(this._partListener);
        window.getShell().removeShellListener(this._partListener);
        this.getSite().getPage().removePartListener(this._partListener);

        if (this._textEditor != null) {
            this._textEditor.removePropertyListener(this);
            this._textEditor.setEditorPart(null);
            this._textEditor.dispose();
        }

        // moved to last when added window ... seems like
        // we'd be in danger of losing some data, like site,
        // or something.
        this._sashEditorPart = null;
        this._tabbedPropSheet = null;
        this._partListener = null;
        this._editDomain = null;
        this._designViewer = null;
        this._browser = null;
        this._previewComposite = null;
        this._paletteViewerPage = null;
        this._log = null;
        this._selChangedListener = null;
        this._textEditor = null;

        if (this._model != null) {
            this._model.releaseFromEdit();
            this._model = null;
        }

        if (this.persistenceListeners != null) {
            this.persistenceListeners.clear();
            this.persistenceListeners = null;
        }

        super.dispose();

    }

    @Override
    public void doSave(IProgressMonitor monitor) {
        this._textEditor.doSave(monitor);
        this.firePersistenceEvent(PersistenceEventType.SAVED);
    }

    /*
     * (non-Javadoc) Saves the contents of this editor to another object. <p>
     * Subclasses must override this method to implement the open-save-close
     * lifecycle for an editor. For greater details, see <code> IEditorPart
     * </code></p>
     *
     * @see IEditorPart
     */
    @Override
    public void doSaveAs() {
        this._textEditor.doSaveAs();
        this.firePersistenceEvent(PersistenceEventType.SAVED_AS);
    }

    private void editorInputIsAcceptable(IEditorInput input)
            throws PartInitException {
        if (input instanceof IFileEditorInput) {
            // verify that it can be opened
            CoreException[] coreExceptionArray = new CoreException[1];
            if (this.fileDoesNotExist((IFileEditorInput) input, coreExceptionArray)) {
                // todo use message formatter for {0}
                Throwable coreException = coreExceptionArray[0];

                // C.B: this is a strange piece of logic.  It was referenceing
                // the internal sub-class of CoreException, ResourceException.
                // need to review fileDoesNotExist.
                if (coreException instanceof CoreException) {
                    // I'm assuming this is always 'does not exist'
                    // we'll refresh local go mimic behavior of default
                    // editor, where the
                    // troublesome file is refreshed (and will cause it to
                    // 'disappear' from Navigator.
                    try {
                        ((IFileEditorInput) input).getFile()
                                .refreshLocal(IResource.DEPTH_ZERO,
                                        new NullProgressMonitor());
                    } catch (CoreException ce) {
                        if (this._log != null) {
                            this._log.error("Error.HTMLEditor.0", ce); //$NON-NLS-1$
                        }
                    }
                    throw new PartInitException("Resource " + input.getName() //$NON-NLS-1$
                            + " does not exist."); //$NON-NLS-1$
                }
                throw new PartInitException("Editor could not be open on " //$NON-NLS-1$
                        + input.getName());
            }
        } else if (input instanceof IStorageEditorInput) {
            InputStream contents = null;
            try {
                contents = ((IStorageEditorInput) input).getStorage()
                        .getContents();
                if (contents == null) {
                    throw new PartInitException("Editor could not be open on " //$NON-NLS-1$
                            + input.getName());
                }
            } catch (CoreException noStorageExc) {
                // Error in geting storage contents
                this._log.error("Error.HTMLEditor.1", noStorageExc); //$NON-NLS-1$
            }
            finally
            {
                ResourceUtils.ensureClosed(contents);
            }
        }
    }

    /**
     * Initializes the editor part with a site and input. <p>
     * Subclasses of <code> EditorPart </code> must implement this method.
     * Within the implementation subclasses should verify that the input type is
     * acceptable and then save the site and input. Here is sample code: </p><pre>
     * if (!(input instanceof IFileEditorInput)) throw new
     * PartInitException("Invalid Input: Must be IFileEditorInput");
     * setSite(site); setInput(editorInput); </pre>
     * @param input
     * @param coreException
     * @return true if the input doesn't exist
     */
    protected boolean fileDoesNotExist(IFileEditorInput input,
            Throwable[] coreException) {
        boolean result = false;
        InputStream inStream = null;
        if ((!(input.exists())) || (!(input.getFile().exists()))) {
            result = true;
        } else {
            try
            {
                inStream = input.getFile().getContents(true);
            }
            catch (CoreException e)
            {
                // very likely to be file not found
                result = true;
                coreException[0] = e;
                // The core has exception
                this._log.error("Error.HTMLEditor.3", e); //$NON-NLS-1$
            }
            finally
            {
                ResourceUtils.ensureClosed(inStream);
            }
        }
        return result;
    }

    @Override
    public Object getAdapter(Class key) {
        System.out.println(">>>>>>>>>>> getAdapter >>>> " + key);

        Object result = null;
        if (key == IDesignViewer.class) {
            result = this._designViewer;
        } else if (key == PalettePage.class) {
            return this.getPaletteViewerPage();
        } else if (key == IPropertySheetPage.class) {
            // XXX: we can delegate this to the fTextEditor, but that use some
            // more
            // complicate mechanism, and don't work with page designer well, so
            // do it simple now, fix later.
            // return _textEditor.getAdapter(key);
            return this.getPropertySheetPage();
        } else if (key == IContentOutlinePage.class) {
            if (this._textEditor != null) {
                result = this._textEditor.getAdapter(key);
            }
        } else if (key == IPageVariablesProvider.class) {
            Object obj = ((IDOMModel)this.getModel()).getDocument().getAdapterFor(
                    IDocumentPageVariableAdapter.class);
            if (obj instanceof IPageVariablesProvider) {
                return obj;
            }
            return null;
        } else {
            // DMW: I'm bullet-proofing this because
            // its been reported (on 4.03 version) a null pointer sometimes
            // happens here on startup, when an editor has been left
            // open when workbench shutdown.
            if (this._textEditor != null) {
                result = this._textEditor.getAdapter(key);
            }
        }
        return result;
    }


    /**
     * IExtendedSimpleEditor method
     * @return IDocument
     */
    public IDocument getDocument() {
        if (this.getTextEditor() == null) {
            return null;
        }

        Object apapter = this._textEditor.getAdapter(ISourceEditingTextTools.class);
        if (apapter != null) {
            return ((ISourceEditingTextTools) apapter).getDocument();
        }

        return null;
    }

    /**
     * IExtendedMarkupEditor method
     * @return the dom document
     */
    public Document getDOMDocument() {
        if (this.getTextEditor() == null) {
            return null;
        }

        Object adapter = this._textEditor.getAdapter(ISourceEditingTextTools.class);
        if (adapter instanceof IDOMSourceEditingTextTools) {
            return ((IDOMSourceEditingTextTools) adapter).getDOMDocument();
        }
        return null;
    }

    /**
     * IExtendedSimpleEditor method
     * @return the editor part
     */
    public IEditorPart getEditorPart() {
        return this;
    }

    /**
     * Caller MUST NOT release this model, it will be released in {@link #dispose()}.
     * @return the structured model
     */
    public IStructuredModel getModel() {
        if (this._model == null) {
            if (this._textEditor != null) {
                IDocumentProvider documentProvider = this._textEditor.getDocumentProvider();
                if (documentProvider != null) {
                    IDocument document = documentProvider.getDocument(this._textEditor.getEditorInput());
                    if (document instanceof IStructuredDocument) {
                        IModelManager modelManager =  StructuredModelManager.getModelManager();
                        if (modelManager != null) {
                            this._model = modelManager.getExistingModelForEdit(document);
                            if (this._model == null) {
                                this._model = modelManager.getModelForEdit((IStructuredDocument)document);
                            }
                        }
                    }
                }
            }
        }
        return this._model;
    }


    /**
     * @return the SSE editor delegate
     */
    public StructuredTextEditor getTextEditor() {
        return this._textEditor;
    }


    /*
     * (non-Javadoc) Method declared on IWorkbenchPart.
     */
    @Override
    public String getTitle() {
        String title = null;
        if (this.getTextEditor() == null) {
            if (this.getEditorInput() != null) {
                title = this.getEditorInput().getName();
            }
        } else {
            title = this.getTextEditor().getTitle();
        }
        if (title == null) {
            title = this.getPartName();
        }
        return title;
    }

    @Override
    public void init(IEditorSite site, IEditorInput input)
            throws PartInitException {
        super.init(site, input);
        this.editorInputIsAcceptable(input);
        try {
            // super.init(site, input);
            // setSite(site);
            this.setInput(input);
            if (this._partListener == null) {
                this._partListener = new PartActivationHandler(this) {
                    @Override
                    public void handleActivation() {
                        HTMLEditor.this.safelySanityCheckState();
                    }
                };
            }
            // we want to listen for our own activation
            IWorkbenchWindow window = this.getSite().getWorkbenchWindow();
            window.getPartService().addPartListener(this._partListener);
            window.getShell().addShellListener(this._partListener);

            // TODO: is this the right place to do this?
            // enable our editor context
            IContextService contextService = (IContextService) this.getSite()
              .getService(IContextService.class);
            contextService.activateContext("com.ust.jst.pagedesigner.editorContext"); //$NON-NLS-1$

        } catch (Exception e) {
            // Error in editor initialization
            this._log.error("Error.HTMLEditor.5", e); //$NON-NLS-1$
        }
        this.setPartName(input.getName());
    }

    /*
     * (non-Javadoc) Returns whether the "save as" operation is supported by
     * this editor. <p> Subclasses must override this method to implement the
     * open-save-close lifecycle for an editor. For greater details, see <code>
     * IEditorPart </code></p>
     *
     * @see IEditorPart
     */
    @Override
    public boolean isSaveAsAllowed() {
        return this._textEditor != null && this._textEditor.isSaveAsAllowed();
    }

    /*
     * (non-Javadoc) Returns whether the contents of this editor should be saved
     * when the editor is closed. <p> This method returns <code> true </code> if
     * and only if the editor is dirty ( <code> isDirty </code> ). </p>
     */
    @Override
    public boolean isSaveOnCloseNeeded() {
        // overriding super class since it does a lowly isDirty!
        if (this._textEditor != null) {
            return this._textEditor.isSaveOnCloseNeeded();
        }
        return this.isDirty();
    }

    /**
     * Posts the update code "behind" the running operation.
     */
    private void postOnDisplayQue(Runnable runnable) {
        IWorkbench workbench = PlatformUI.getWorkbench();
        IWorkbenchWindow[] windows = workbench.getWorkbenchWindows();
        if (windows != null && windows.length > 0) {
            Display display = windows[0].getShell().getDisplay();
            display.asyncExec(runnable);
        } else {
            runnable.run();
        }
    }

    /**
     * Indicates that a property has changed.
     *
     * @param source
     *            the object whose property has changed
     * @param propId
     *            the id of the property which has changed; property ids are
     *            generally defined as constants on the source class
     */
    @Override
    public void propertyChanged(Object source, int propId) {
        switch (propId) {
        // had to implement input changed "listener" so that
        // strucutedText could tell it containing editor that
        // the input has change, when a 'resource moved' event is
        // found.
        case IEditorPart.PROP_INPUT: {
            if (source == this._textEditor) {
                if (this._textEditor.getEditorInput() != this.getEditorInput()) {
                    //Bug 392859 - [Regression] Incorrect WPE model returned from HTMLEditor after page name change.
                    // release the old model
                    if (this._model != null) {
                        this._model.releaseFromEdit();
                        this._model = null;
                    }
                    this.setInput(this._textEditor.getEditorInput());
                    // title should always change when input changes.
                    // create runnable for following post call
                    Runnable runnable = new Runnable() {
                        @Override
                        public void run() {
                            HTMLEditor.this._firePropertyChange(IWorkbenchPart.PROP_TITLE);
                        }
                    };
                    // Update is just to post things on the display queue
                    // (thread). We have to do this to get the dirty
                    // property to get updated after other things on the
                    // queue are executed.
                    this.postOnDisplayQue(runnable);
                }
            }
            break;
        }
        case IWorkbenchPart.PROP_TITLE: {
            // // update the input if the title is changed. why? It seems input
            // change event will be fired at last.
            // if (source == _textEditor)
            // {
            // if (_textEditor.getEditorInput() != getEditorInput())
            // {
            // setInput(_textEditor.getEditorInput());
            // }
            // }
            // break;
        }
        default: {
            // propagate changes. Is this needed? Answer: Yes.
            // PROP_PART_NAME, PROP_DIRTY etc.
            if (source == this._textEditor) {
                this.firePropertyChange(propId);
            }
            break;
        }
        }

    }

    private void safelySanityCheckState() {
        // If we're called before editor is created, simply ignore since we
        // delegate this function to our embedded TextEditor
        if (this.getTextEditor() == null) {
            return;
        }

        this.getTextEditor().safelySanityCheckState(this.getEditorInput());

    }

    /*
     * (non-Javadoc)
     *
     * @see org.eclipse.ui.part.EditorPart#setInput(org.eclipse.ui.IEditorInput)
     */
    @Override
    protected void setInput(IEditorInput input) {
        // If driven from the Source page, it's "model" may not be up to date
        // with the input just yet. We'll rely on later notification from the
        // TextViewer to set us straight
        super.setInput(input);
        if (this._designViewer != null) {
            this._designViewer.setModel(this.getModel());
        }
        this.setPartName(input.getName());
    }

    /*
     * (non-Javadoc)
     *
     * @see org.eclipse.ui.part.EditorPart#isDirty()
     */
    @Override
    public boolean isDirty() {
        if (this.getTextEditor() == null) {
            return false;
        }
        return this.getTextEditor().isDirty();
    }

    private IPropertySheetPage getPropertySheetPage()
    {
        if (this._tabbedPropSheet == null || this._tabbedPropSheet.getControl() == null
                || this._tabbedPropSheet.getControl().isDisposed())
        {
            IPropertySheetPageFactory factory = this.getPageFactory();
            if (factory != null)
            {
                final IFile file = ((IFileEditorInput)this.getEditorInput()).getFile();
                this._tabbedPropSheet = factory.createPage(file);
            }
            else
            {
                this._tabbedPropSheet = new WPETabbedPropertySheetPage(this,this);
            }
        }
        return this._tabbedPropSheet;
    }

    private IPropertySheetPageFactory getPageFactory()
    {
        //List<IElementEditFactory> result = new ArrayList<IElementEditFactory>();
        IExtensionPoint extensionPoint = Platform.getExtensionRegistry()
                .getExtensionPoint(PDPlugin.getPluginId(),
                        IJMTConstants.EXTENSION_POINT_PAGEDESIGNER);
        IExtension[] extensions = extensionPoint.getExtensions();

        for (IExtension ext : extensions) {
            IConfigurationElement[] elementEditElement = ext
                    .getConfigurationElements();

            for (final IConfigurationElement element : elementEditElement) {
                if (element.getName().equals(
                        IJMTConstants.PROPERTY_PAGE_FACTORY))
                {
                    element.getAttribute("class"); //$NON-NLS-1$
                    Object obj;
                    try
                    {
                        obj = element
                                .createExecutableExtension("class"); //$NON-NLS-1$

                        // TODO: we need a policy based solution here,
                        // but this will do for now
                        if (obj instanceof IPropertySheetPageFactory)
                        {
                            return (IPropertySheetPageFactory) obj;
                        }
                    }
                    catch (CoreException e)
                    {
                        PDPlugin.log("Problem loading element edit extension for "+element.toString(), e); //$NON-NLS-1$
                    }
                }
            }
        }
        return null;
    }

    /**
     * @return PaletteViewerPage
     */
    private PaletteViewerPage getPaletteViewerPage()
    {
        if (this._paletteViewerPage == null)
        {
            this._paletteViewerPage = this._designViewer.createPaletteViewerPage();
        }
        return this._paletteViewerPage;
    }

    /**
     * @return the edit domain
     */
    public DefaultEditDomain getEditDomain() {
        if (this._editDomain == null) {
            this._editDomain = new DefaultEditDomain(this);

            // XXX: if i don't do the following line, system will default use
            // SelectionTool. Don't know where else to set this. Since it is
            // kind of duplicate
            // to the DesignerPaletteRoot.
            this._editDomain.setDefaultTool(new RangeSelectionTool());
            this._editDomain.loadDefaultTool();

            // next config the _editDomain
            // _editDomain.setPaletteRoot(new JSFPaletteRoot());
        }
        return this._editDomain;
    }

    /**
     * (non-Javadoc)
     *
     * @see org.eclipse.ui.part.MultiPageEditorPart#pageChange(int)
     */
    @Override
    protected void pageChange(int newPageIndex) {
        super.pageChange(newPageIndex);

        this.deletePreviewFiles();

        if (newPageIndex == this._previewPageIndex) {
            // preview page activate, need to regenerate the preview text and
            // display it.
            StringBuffer result = new StringBuffer();
            try {
                //Bug 350990 - Web Page Editor intolerably slow
                if (this._mode == MODE_SOURCE) {
                    this._designViewer.setModel(this.getModel());
                }
                DocumentEditPart part = (DocumentEditPart) this._designViewer
                        .getGraphicViewer().getContents();
                PreviewHandlerNew.generatePreview(part, result);
                //Bug 350990 - Web Page Editor intolerably slow
                if (this._mode == MODE_SOURCE) {
                    this._designViewer.setModel(null);
                }
            } catch (Exception ex) {
                result = new StringBuffer();
                result.append(this.getModel().getStructuredDocument().getText());
                // Error in page changing
                this._log.info("Error.HTMLEditor.6", ex); //$NON-NLS-1$
            }
            File file = PreviewUtil.toFile(result, this.getEditorInput());
            if (file != null) {
                this.PREVIEW_FILES_LIST.add(file);
                this.getPreviewBrowser().loadFile(file);
            } else {
                this.getPreviewBrowser().getBrowser().setUrl("about:blank"); //$NON-NLS-1$
            }
        }
    }

    /**
     * @return Returns the _designViewer.
     */
    public IDesignViewer getDesignViewer() {
        return this._designViewer;
    }

    /**
     * @param mode
     */
    public void setDesignerMode(int mode) {
        boolean modeWasSourceOnly = (this._mode == MODE_SOURCE && this._mode != mode);
        if (this._sashEditorPart != null && this._mode != mode) {
            switch (mode) {
            case MODE_SASH_HORIZONTAL:
                this._sashEditorPart.setOrientation(SWT.HORIZONTAL);
                break;
            case MODE_DESIGNER:
                this._sashEditorPart.setMaximizedEditor(this._designViewer);
                break;
            case MODE_SOURCE:
                this._sashEditorPart.setMaximizedEditor(this._textEditor);
                //Bug 350990 - Web Page Editor intolerably slow
                this._designViewer.setModel(null);
                if (this._designPageActionContributor != null) {
                    this._designPageActionContributor.disableRangeModeActions();
                }
                break;
            case MODE_SASH_VERTICAL:
            default:
                this._sashEditorPart.setOrientation(SWT.VERTICAL);
            }
            if (this.getEditorInput() != null) {
                EditorUtil.setEditorInputDesignModeProperty(this.getEditorInput(), String.valueOf(mode));
            }
        }
        this._mode = mode;
        if (modeWasSourceOnly) {
            //Bug 350990 - Web Page Editor intolerably slow
            this._designViewer.setModel(this.getModel());
            this.resynch();
        }
    }

    /**
     * Sets the current DesignPageActionContributor instance.
     * @param designPageActionContributor Current DesignPageActionContributor instance.
     */
    public void setDesignPageActionContributor(
            final DesignPageActionContributor designPageActionContributor) {
        this._designPageActionContributor = designPageActionContributor;
    }

    /*
     * Set the sash editor mode from the stored file property
     * or the default preference.
     */
    private void initDesignerMode() {
        int preferredMode = MODE_SASH_VERTICAL;

        // If the user has already selected a mode for the file, use it.
        String prop = null;
        if (this.getEditorInput() != null) {
            prop = EditorUtil.getEditorInputDesignModeProperty(this.getEditorInput());
        }
        if (prop != null) {
            try {
                preferredMode = Integer.parseInt(prop);
            } catch (NumberFormatException e) {
                // do nothing;
            }
        } else {
            // Otherwise, get the default mode from preferences.
            IPreferenceStore pStore = PDPlugin.getDefault().getPreferenceStore();
            preferredMode = pStore.getInt(PDPreferences.SASH_EDITOR_MODE_PREF);
        }

        this.setDesignerMode(preferredMode);
    }

    /**
     * @return the current design mode
     */
    public int getDesignerMode() {
        return this._mode;
    }

    private void resynch() {
        if (this._textEditor != null && this._designViewer != null) {
            ISelectionProvider provider = this._textEditor.getSelectionProvider();
            if (provider != null) {
                ISelection selection = provider.getSelection();
                if (selection instanceof TextSelection) {
                    TextSelection textSelection = (TextSelection)selection;
                    SelectionSynchronizer synchronizer = this._designViewer.getSynchronizer();
                    if (synchronizer != null) {
                        synchronizer.textSelectionChanged(
                                textSelection.getOffset(),
                                textSelection.getOffset() + textSelection.getLength());
                    }
                }
            }
        }
    }

    @Override
    public IEditorPart getActiveEditor() {
        IEditorPart result = null;
        if (this._sash) {
            result = this._sashEditorPart.getActiveEditor();
        } else {
            if (this._designViewer.getGraphicViewer().getControl().isFocusControl()) {
                result = this._designViewer;
            } else if (this._textEditor.getTextViewer().getControl()
                    .isFocusControl()) {
                result = this._textEditor;
            }
        }
        return result;
    }

    @Override
    public String getPartName() {
        if (this._textEditor != null) {
            return this._textEditor.getPartName();
        }
        return super.getPartName();
    }

    private void deletePreviewFiles() {
        Iterator itPreviewFiles = this.PREVIEW_FILES_LIST.iterator();
        while (itPreviewFiles.hasNext()) {
            File file = (File)itPreviewFiles.next();
            if (file != null && file.exists()) {
                file.delete();
            }
        }
        this.PREVIEW_FILES_LIST.clear();
    }

    /**
     * Refreshes the design page. Allows an external action to force a refresh
     * after an external change, such as a DT skin change.
     */
    public void refreshDesignViewer() {
        EditPart contentEditPart = this._designViewer.getGraphicViewer().getRootEditPart().getContents();
        if (contentEditPart instanceof DocumentEditPart) {
            ((DocumentEditPart)contentEditPart).styleChanged();
        }
    }

    /**
     * Adds a {@link IWPEPersistenceListener persistence listener} to this editor.
     *
     * <p>This type of listener is cleaned when the editor is disposed.</p>
     *
     * @param listener
     */
    public void addPersistenceListener(IWPEPersistenceListener listener) {
        if (this.persistenceListeners == null) {
            this.persistenceListeners = new ArrayList<IWPEPersistenceListener>(5);
        }
        this.persistenceListeners.add(listener);
    }

    /**
     * Removes a {@link IWPEPersistenceListener persistence listener} added to this
     * editor.
     *
     * <p>This type of listener is cleaned when the editor is disposed.</p>
     *
     * @param listener
     */
    public void removePersistenceListener(IWPEPersistenceListener listener) {
        if (this.persistenceListeners != null) {
            if (this.persistenceListeners.remove(listener) && this.persistenceListeners.isEmpty()) {
                this.persistenceListeners = null;
            }
        }
    }

    private void firePersistenceEvent(final PersistenceEventType type) {
        if (this.persistenceListeners != null) {
            List<IWPEPersistenceListener> listeners = new ArrayList<IWPEPersistenceListener>(this.persistenceListeners);
            IPersistenceEvent event = new IPersistenceEvent() {
                @Override
                public HTMLEditor getWPEInstance() {
                    return HTMLEditor.this;
                }

                @Override
                public PersistenceEventType getEventType() {
                    return type;
                }
            };

            for (IWPEPersistenceListener listener : listeners) {
                try {
                    listener.notify(event);
                } catch (Exception e) {
                    PDPlugin.log("Exception thrown while notifying a persistence listener", e); //$NON-NLS-1$
                }
            }
        }
    }

}
